package com.syscom.server.qd.cacheManager;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import com.syscom.frmcommon.util.ArithUtil;
import com.syscom.server.cache.safeCache.vo.ServerStatusCacheVo;
import com.syscom.safe.cache.SafeCache;
import com.syscom.safe.cache.SafeCacheException;
import com.syscom.safe.cache.SafeCacheManager;
import com.syscom.safe.cache.SafeCacheVo;
import com.syscom.safe.tcplink.client.api.SafeTcpLinkApi;
import com.syscom.safe.tcplink.client.api.SafeTcpLinkApiException;
import com.syscom.safe.wac.buf.SacOutputBuffer;
import com.syscom.safe.wac.management.SacManager;
import com.syscom.safe.wac.routing.HubNodeManager;
import com.syscom.safe.wac.txbean.TxContext;
import com.syscom.sar.server.constant.QDCTopicConstant;
import com.syscom.server.cache.safeCache.vo.RuleQtaHandleCacheVo;
import com.syscom.server.constant.CacheConstant;
import com.syscom.server.constant.QDCodeConstant;
import com.syscom.server.qd.QdQuotaFactory;
import com.syscom.server.qd.util.QuotaCalculateUtil;
import com.syscom.server.qd.vo.RuleQtaStatusVo;
import com.syscom.shared.constant.CodeConstant;

/**
 * 各條件全公司額度控管
 * 
 * @author Jean
 */
public class RuleQtaCacheManager {
	
	private static Logger mLogger = Logger.getLogger(RuleQtaCacheManager.class.getName());
	
	private static RuleQtaCacheManager instance = new RuleQtaCacheManager();
	
	private Map<Integer, Integer> mapQuotaUnit;
	
	private List<Integer> lstLockProdRuleIds = new ArrayList<Integer>();
	private List<Integer> lstNonLockProdRuleIds = new ArrayList<Integer>();
	
	// 記錄資料來源 是從DB來的不需要再算單位
	private Set<String> setDataFromDB = new HashSet<String>();
	
	public static RuleQtaCacheManager getInstance() { return instance; }
	
	private RuleQtaCacheManager() {
		TxContext ctx = new TxContext(0, null);
		
		try {
			// Get Lock Prod Rule id
			lstLockProdRuleIds = QdQuotaFactory.getQuotaDao(ctx).queryRuleIds(ctx, true);
			// Non Lock Prod
			lstNonLockProdRuleIds = QdQuotaFactory.getQuotaDao(ctx).queryRuleIds(ctx, false);
		
		} catch (Exception ex) {
			mLogger.fatal("Init Quota Handle Failed. Cause: " + ex.getMessage());
			ex.printStackTrace();
		}
	}
	
	public void initAfterGlobalConfig() {
		TxContext ctx = new TxContext(0, null);
		
		try {
			// Global
			RuleQtaHandleCacheVo[] globalQuotaHandles = QdQuotaFactory.getQuotaDao(ctx).queryGlobalQuota(ctx);
			insertRuleQtaHandleCacheVos(globalQuotaHandles);
			mLogger.debug("Init Global Quota Success.");
			
			// Lock Prod Duplicate
			String[] sProdIds = QdQuotaFactory.getQuotaDao(ctx).queryAllProdId(ctx);
			initAllLockProdRules(sProdIds);
			mLogger.debug("Init All Lock Product Rules Success.");
			
			// Zoom
			RuleQtaHandleCacheVo[] zoomQuotaHandles = QdQuotaFactory.getQuotaDao(ctx).queryZoomQuota(ctx);
			updateZoomRuleQtaHandleCacheVos(zoomQuotaHandles);
			mLogger.debug("Init Zoom Quota Success.");
			
			// 單位
			mapQuotaUnit = QdQuotaFactory.getQuotaDao(ctx).queryQuotaUnit(ctx);
			mulQuotaWithUnit(mapQuotaUnit);
			mLogger.debug("Init Quota Unit Success.");
			
			// check trigger compa
			checkTriggerCompa();
			
			// Init Remaing Qta 一開始依照CONFIG可知道QD剩餘額將會是多少 不用等到單子來了才算
			initRemaingQta();
			mLogger.debug("Init Remaining Quota Success.");
			
			// 強迫所有VO 寫DB
			
		} catch (Exception ex) {
			mLogger.fatal("Init Quota Handle Failed. Cause: " + ex.getMessage());
			ex.printStackTrace();
		}
	}

	private List<SafeCacheVo> getRuleQtaHandleCacheVosByRuleForReading(int iRuleId) {
		List<SafeCacheVo> lst = null;
		try {
			lst = SafeCacheManager.getInstance().getCache(CacheConstant.QTA_HANDLE_CACHE).getVosForReading(CacheConstant.SEARCH_BY_RULE, String.valueOf(iRuleId));
		} catch (SafeCacheException ex) {
			mLogger.error("Get RuleQtaHandleCacheVo for Reading Failed. Cause: " + ex.getMessage());
			ex.printStackTrace();
		}
		return lst;
	}
	
	private List<SafeCacheVo> getRuleQtaHandleCacheVosByRuleForUpdating(int iRuleId) {
		List<SafeCacheVo> lst = null;
		try {
			lst = SafeCacheManager.getInstance().getCache(CacheConstant.QTA_HANDLE_CACHE).getVosForUpdating(CacheConstant.SEARCH_BY_RULE, String.valueOf(iRuleId));
		} catch (SafeCacheException ex) {
			mLogger.error("Get RuleQtaHandleCacheVo for Updating Failed. Cause: " + ex.getMessage());
			ex.printStackTrace();
		}
		return lst;
	}
	
	private void releaseRuleQtaHandleCacheVosFromReading(List<SafeCacheVo> lst) {
		try {
			SafeCacheManager.getInstance().getCache(CacheConstant.QTA_HANDLE_CACHE).releaseVosFromReading(lst);
		} catch (SafeCacheException ex) {
			mLogger.error("Release RuleQtaHandleCacheVos from Reading Failed. Cause: " + ex.getMessage());
			ex.printStackTrace();
		}
	}
	
	private void releaseRuleQtaHandleCacheVosFromUpdating(List<SafeCacheVo> lst) {
		try {
			SafeCacheManager.getInstance().getCache(CacheConstant.QTA_HANDLE_CACHE).releaseVosFromUpdating(lst);
		} catch (SafeCacheException ex) {
			mLogger.error("Release RuleQtaHandleCacheVos from Updating Failed. Cause: " + ex.getMessage());
			ex.printStackTrace();
		}
	}
	
	private RuleQtaHandleCacheVo getRuleQtaHandleCacheVoForUpdating(int iRuleId, String sProdId) {
		RuleQtaHandleCacheVo vo = null;
		String sKey = RuleQtaHandleCacheVo.getKeyString(iRuleId, sProdId);
		
		try {
			vo = (RuleQtaHandleCacheVo) SafeCacheManager.getInstance().getCache(CacheConstant.QTA_HANDLE_CACHE).getVoForUpdating(sKey);
		} catch (SafeCacheException ex) {
			mLogger.error("Get RuleQtaHandleCacheVo for Updating Failed. Cause: " + ex.getMessage());
			ex.printStackTrace();
		}
		return vo;
	}
	
	private void releaseRuleQtaHandleCacheVoFromUpdating(RuleQtaHandleCacheVo vo) {
		if(vo != null) {
			try {
				SafeCacheManager.getInstance().getCache(CacheConstant.QTA_HANDLE_CACHE).releaseVoFromUpdating(vo);
			} catch (SafeCacheException ex) {
				mLogger.error("Release RuleQtaHandleCacheVo from Updating Failed. Cause: " + ex.getMessage());
				ex.printStackTrace();
			}
		}
	}
	
	private void insertRuleQtaHandleCacheVos(RuleQtaHandleCacheVo[] vos) {
		SafeCache safeCache = SafeCacheManager.getInstance().getCache(CacheConstant.QTA_HANDLE_CACHE);
		
		for(int i = 0 ; i < vos.length ; i++) {
			try {
				if(!safeCache.insertVoIfNotExist(vos[i])) {
					// 
					mLogger.debug("RuleQtaHandleCacheVo already exist. VO: " + vos[i].toString());
					
					// 資料是從DB來的 表示單位已經換算過 不需要再算記在這
					String sKey = RuleQtaHandleCacheVo.getKeyString(vos[i].getRuleId(), vos[i].getProdId());
					setDataFromDB.add(sKey);
				} else {
					mLogger.debug("Insert RuleQtaHandleCacheVo. RuleId:[" + vos[i].getRuleId() + "], ProdId:[" + vos[i].getProdId() + "], Quotas:[" + vos[i].getWarnQuota() + "],[" + vos[i].getSubQuota() + "],[" + vos[i].getRejQuota() + "]");
				}
			} catch (SafeCacheException ex) {
				mLogger.error("Insert RuleQtaHandleCacheVo to SafeCache Failed. Cause: " + ex.getMessage());
				ex.printStackTrace();
			}
		}
	}
	
	private void insertRuleQtaHandleCacheVo(RuleQtaHandleCacheVo vo) {
		SafeCache safeCache = SafeCacheManager.getInstance().getCache(CacheConstant.QTA_HANDLE_CACHE);
		
			try {
				safeCache.insertVoIfNotExist(vo);
			} catch (SafeCacheException ex) {
				mLogger.error("Insert RuleQtahandleCacheVo to SafeCache Failed. Cause: " + ex.getMessage());
				ex.printStackTrace();
			}
	}
	
	private void initAllLockProdRules(String[] sProdIds) {
		for(Integer iRuleId : lstLockProdRuleIds) {
			RuleQtaHandleCacheVo vo = null;
			try {
				vo = getRuleQtaHandleCacheVoForUpdating(iRuleId, "");
				if(vo != null) {
					for(int i = 0 ; i < sProdIds.length ; i++) {
						RuleQtaHandleCacheVo lockProdVo = new RuleQtaHandleCacheVo();
						lockProdVo.copyFields(vo);
						lockProdVo.setProdId(sProdIds[i]);
						insertRuleQtaHandleCacheVo(lockProdVo);
					}
				}
			} finally {
				releaseRuleQtaHandleCacheVoFromUpdating(vo);
			}
		}
	}
	
	private ServerStatusCacheVo getServerStatusCacheVoForReading(String sServerId) {
		ServerStatusCacheVo vo = null;
		try {
			vo = (ServerStatusCacheVo) SafeCacheManager.getInstance().getCache(CacheConstant.SERVER_STATUS).getVoForReading(sServerId);
		} catch (SafeCacheException ex) {
			mLogger.error("Get ServerStatusCacheVo for Reading Failed. Cause: " + ex.getMessage());
			ex.printStackTrace();
		}
		return vo;
	}

	private void releaseServerStatusCacheVoFromReading(ServerStatusCacheVo vo) {
		if(vo != null) {
			try {
				SafeCacheManager.getInstance().getCache(CacheConstant.SERVER_STATUS).releaseVoFromReading(vo);
			} catch (SafeCacheException ex) {
				mLogger.error("Release ServerStatusCacheVo From Reading Failed. Cause: " + ex.getMessage());
				ex.printStackTrace();
			}
		}
	}
	
	private void insertServerStatusCacheVo(ServerStatusCacheVo vo) {
		try {
			SafeCacheManager.getInstance().getCache(CacheConstant.SERVER_STATUS).insertVoIfNotExist(vo);
		} catch (SafeCacheException ex) {
			mLogger.error("Insert ServerStatusCacheVo to SafeCache Failed. Cause: " + ex.getMessage());
			ex.printStackTrace();
		}
	}
	
	private void updateZoomRuleQtaHandleCacheVos(RuleQtaHandleCacheVo[] vos) {
		for(int i = 0 ; i < vos.length ; i++) {
			int ruleId = vos[i].getRuleId();
			String prodId = vos[i].getProdId();
			RuleQtaHandleCacheVo vo = null;
			
			try {
				vo = getRuleQtaHandleCacheVoForUpdating(ruleId, prodId);
				if(vo == null) {
					mLogger.error("No Global Quota Setting. Can't zoom Quota. RuleId:[" + vos[i].getRuleId() + "], ProdId:[" + vos[i].getProdId() + "]");
				} else {
					String sKey = RuleQtaHandleCacheVo.getKeyString(vos[i].getRuleId(), vos[i].getProdId());
					// 如果SET裡面有 就表示從DB撈的 因此其時也放寬縮減過(如果QD關機的時候有異動過放寬縮減 就會不適用,這樣做是怕蓋掉Dispatch_QTA)
					if(!setDataFromDB.contains(sKey)) {
						if(vos[i].getWarnQuota() != QDCodeConstant.QUOTA_ZOOM_NO_VALUE) {
							vo.setWarnQuota(vos[i].getWarnQuota());
						}
						if(vos[i].getSubQuota() != QDCodeConstant.QUOTA_ZOOM_NO_VALUE) {
							vo.setSubQuota(vos[i].getSubQuota());
						}
						if(vos[i].getRejQuota() != QDCodeConstant.QUOTA_ZOOM_NO_VALUE) {
							vo.setRejQuota(vos[i].getRejQuota());
						}
						
						mLogger.debug("Update RuleQtaHandleCacheVo by Zoom. RuleId:[" + vos[i].getRuleId() + "], ProdId:[" + vos[i].getProdId() + "], Quotas:[" + vos[i].getWarnQuota() + "],[" + vos[i].getSubQuota() + "],[" + vos[i].getRejQuota() + "]");
					}
				}
			} finally {
				releaseRuleQtaHandleCacheVoFromUpdating(vo);
			}
		}
	}
	
	private void mulQuotaWithUnit(Map<Integer, Integer> map) {
		SafeCache safeCache = SafeCacheManager.getInstance().getCache(CacheConstant.QTA_HANDLE_CACHE);	
		
		List<SafeCacheVo> lst = safeCache.getValueList();
		
		for(SafeCacheVo safeCacheVo : lst) {
			RuleQtaHandleCacheVo vo = (RuleQtaHandleCacheVo) safeCacheVo;
			int iRuleId = vo.getRuleId();
			String sProdId = vo.getProdId();
			String sKey = RuleQtaHandleCacheVo.getKeyString(iRuleId, sProdId);
			RuleQtaHandleCacheVo ruleQtaHandleCacheVo = null;
			
			try {
				ruleQtaHandleCacheVo = getRuleQtaHandleCacheVoForUpdating(iRuleId, sProdId);
				
				if(!setDataFromDB.contains(sKey)) {
					if(map.containsKey(iRuleId)) {
						int iQuotaUnit = map.get(iRuleId);
						
						double dWarnQuota = ArithUtil.mul(ruleQtaHandleCacheVo.getWarnQuota(), iQuotaUnit);
						double dSubQuota = ArithUtil.mul(ruleQtaHandleCacheVo.getSubQuota(), iQuotaUnit);
						double dRejQuota = ArithUtil.mul(ruleQtaHandleCacheVo.getRejQuota(), iQuotaUnit);
						ruleQtaHandleCacheVo.setWarnQuota(dWarnQuota);
						ruleQtaHandleCacheVo.setSubQuota(dSubQuota);
						ruleQtaHandleCacheVo.setRejQuota(dRejQuota);
						
//						mLogger.debug("Multiple Quota with QuotaUnit. RuleId:[" + iRuleId + "], ProdId:[" + ruleQtaHandleCacheVo.getProdId() +"], Quota:[" + ruleQtaHandleCacheVo.getWarnQuota() + "], [" + ruleQtaHandleCacheVo.getSubQuota() + "], [" + ruleQtaHandleCacheVo.getRejQuota() + "]");
					}
				} else {
//					mLogger.debug("Data already exist in DB. Don't need to Multiple Quota with QuotaUnit. RuleId:[" + iRuleId + "], ProdId:[" + ruleQtaHandleCacheVo.getProdId() +"], Quota:[" + ruleQtaHandleCacheVo.getWarnQuota() + "], [" + ruleQtaHandleCacheVo.getSubQuota() + "], [" + ruleQtaHandleCacheVo.getRejQuota() + "]");
				}
			} finally {
				releaseRuleQtaHandleCacheVoFromUpdating(ruleQtaHandleCacheVo);
			}
		}
	}
	
	private void checkTriggerCompa() {
		SafeCache safeCache = SafeCacheManager.getInstance().getCache(CacheConstant.QTA_HANDLE_CACHE);
		
		List<SafeCacheVo> lst = safeCache.getValueList();
		
		// 如果是>= , Quota-1
		for(SafeCacheVo safeCacheVo : lst) {
			RuleQtaHandleCacheVo vo = (RuleQtaHandleCacheVo) safeCacheVo;
			int iRuleId = vo.getRuleId();
			String sProdId = vo.getProdId();
			String sKey = RuleQtaHandleCacheVo.getKeyString(iRuleId, sProdId);
			RuleQtaHandleCacheVo ruleQtaHandleCacheVo = null;
			
			try {
				ruleQtaHandleCacheVo = getRuleQtaHandleCacheVoForUpdating(iRuleId, sProdId);
				
				if(!setDataFromDB.contains(sKey)) {
					String sWarnCompa = ruleQtaHandleCacheVo.getWarnTriggerCompa();
					String sSubCompa = ruleQtaHandleCacheVo.getSubTriggerCompa();
					String sRejCompa = ruleQtaHandleCacheVo.getRejTriggerCompa();
					
					if(CodeConstant.TRIGGER_COMPA_GRAEATER_THAN_OR_EQUAL == sWarnCompa.charAt(0)) {
						double dWarnQuota = ruleQtaHandleCacheVo.getWarnQuota();
						dWarnQuota = ArithUtil.sub(dWarnQuota, 1);
						ruleQtaHandleCacheVo.setWarnQuota(dWarnQuota);
					}
					if(CodeConstant.TRIGGER_COMPA_GRAEATER_THAN_OR_EQUAL == sSubCompa.charAt(0)) {
						double dSubQuota = ruleQtaHandleCacheVo.getSubQuota();
						dSubQuota = ArithUtil.sub(dSubQuota, 1);
						ruleQtaHandleCacheVo.setWarnQuota(dSubQuota);
					}
					if(CodeConstant.TRIGGER_COMPA_GRAEATER_THAN_OR_EQUAL == sRejCompa.charAt(0)) {
						double dRejQuota = ruleQtaHandleCacheVo.getRejQuota();
						dRejQuota = ArithUtil.sub(dRejQuota, 1);
						ruleQtaHandleCacheVo.setWarnQuota(dRejQuota);
					}
				} else {
					
				}
			} finally {
				releaseRuleQtaHandleCacheVoFromUpdating(ruleQtaHandleCacheVo);
			}
		}
	}
	
	private void initRemaingQta() {
		SafeCache safeCache = SafeCacheManager.getInstance().getCache(CacheConstant.QTA_HANDLE_CACHE);
		
		List<SafeCacheVo> lst = safeCache.getValueList();
		
		for(SafeCacheVo safeCacheVo : lst) {
			RuleQtaHandleCacheVo vo = (RuleQtaHandleCacheVo) safeCacheVo;
			int iRuleId = vo.getRuleId();
			String sProdId = vo.getProdId();
			String sKey = RuleQtaHandleCacheVo.getKeyString(iRuleId, sProdId);
			RuleQtaHandleCacheVo ruleQtaHandleCacheVo = null;
			
			try {
				ruleQtaHandleCacheVo = getRuleQtaHandleCacheVoForUpdating(iRuleId, sProdId);
				
				if(!setDataFromDB.contains(sKey)) {
					String[] sServerId = GlobalConfigManager.getInstance().getServerIdList();
					
					double dRateSum = 0;
					for(int i = 0 ; i < sServerId.length ; i++) {
						dRateSum = ArithUtil.add(dRateSum, GlobalConfigManager.getInstance().getRatio(sServerId[i], iRuleId));
					}
					
					// 剩餘量比率
					dRateSum = ArithUtil.sub(1, dRateSum);
					
					double dThreshold = GlobalConfigManager.getInstance().getThreshold();
					if(ruleQtaHandleCacheVo.getWarnTriggerMk()) {
						double dQuota = QuotaCalculateUtil.getInstance().calcQuotaWithRate(ruleQtaHandleCacheVo.getWarnQuota(), dRateSum);
						dQuota = QuotaCalculateUtil.getInstance().calcQuotaWithRate(dQuota, dThreshold);
						ruleQtaHandleCacheVo.setThresholdQta(dQuota);
					} else if(ruleQtaHandleCacheVo.getSubTriggerMk()) {
						double dQuota = QuotaCalculateUtil.getInstance().calcQuotaWithRate(ruleQtaHandleCacheVo.getSubQuota(), dRateSum);
						dQuota = QuotaCalculateUtil.getInstance().calcQuotaWithRate(dQuota, dThreshold);
						ruleQtaHandleCacheVo.setThresholdQta(dQuota);
					} else if(ruleQtaHandleCacheVo.getRejTriggerMk()) {
						double dQuota = QuotaCalculateUtil.getInstance().calcQuotaWithRate(ruleQtaHandleCacheVo.getRejQuota(), dRateSum);
						dQuota = QuotaCalculateUtil.getInstance().calcQuotaWithRate(dQuota, dThreshold);
						ruleQtaHandleCacheVo.setThresholdQta(dQuota);
					}
				} else {
					
				}
			} finally {
				releaseRuleQtaHandleCacheVoFromUpdating(ruleQtaHandleCacheVo);
			}
		}
	}
	
	public RuleQtaStatusVo[] getInitQuotaByServerId(String sServerId) {
		List<RuleQtaStatusVo> lst = new ArrayList<RuleQtaStatusVo>();
		RuleQtaStatusVo[] vos = null;
		/** 
		 * 檢查ServerId是否在Cache中
		 * 在表示取過
		 */
		ServerStatusCacheVo serverStatusCacheVo = null;
		try {
			serverStatusCacheVo = getServerStatusCacheVoForReading(sServerId);
			if(serverStatusCacheVo != null) {
				mLogger.debug("ServerId:[" + sServerId + "] already get Quota before.");
				return new RuleQtaStatusVo[0];
			}
		} finally {
			releaseServerStatusCacheVoFromReading(serverStatusCacheVo);
		}
		
		List<Integer> lstRules = getAllRuleIds();
			
		for(Integer iRuleId : lstRules) {
			List<SafeCacheVo> lstVos = null;
			try {
				// 取得RULE的所有限額
				lstVos = getRuleQtaHandleCacheVosByRuleForUpdating(iRuleId);
				if(lstVos != null) {
					for(SafeCacheVo vo : lstVos) {
						RuleQtaHandleCacheVo ruleVo = (RuleQtaHandleCacheVo) vo;
						RuleQtaStatusVo ruleQtaStatusCacheVo = new RuleQtaStatusVo();
						
						String sProdId = ruleVo.getProdId();
						
						ruleQtaStatusCacheVo.setRuleId(iRuleId);
						ruleQtaStatusCacheVo.setProdId(sProdId);
						
						double dRatio = GlobalConfigManager.getInstance().getRatio(sServerId, iRuleId);
						double dQuota = 0;
						
						if(ruleVo.getWarnTriggerMk()) {
							dQuota = QuotaCalculateUtil.getInstance().calcQuotaWithRate(ruleVo.getWarnQuota(), dRatio);
						} else if(ruleVo.getSubTriggerMk()) {
							dQuota = QuotaCalculateUtil.getInstance().calcQuotaWithRate(ruleVo.getSubQuota(), dRatio);
						} else if(ruleVo.getRejTriggerMk()) {
							dQuota = QuotaCalculateUtil.getInstance().calcQuotaWithRate(ruleVo.getRejQuota(), dRatio);
						}
						
						// 分出去的
						double dDispatchQuota = ruleVo.getDispatchQuota();
						dDispatchQuota = ArithUtil.add(dDispatchQuota, dQuota);
						ruleVo.setDispatchQuota(dDispatchQuota);
						
						// 初始的
						ruleQtaStatusCacheVo.setDispatchQta(dQuota);

						mLogger.debug("[" + sServerId + "] take [" + dQuota + "] from QD. RuleId:[" + iRuleId + "], ProdId:[" + sProdId + "]");
						
						lst.add(ruleQtaStatusCacheVo);
					}
				}
			} finally {
				releaseRuleQtaHandleCacheVosFromUpdating(lstVos);
			}
		}
		
		// 新增到CACHE表示拿過
		serverStatusCacheVo = new ServerStatusCacheVo();
		serverStatusCacheVo.setServerId(sServerId);
		insertServerStatusCacheVo(serverStatusCacheVo);
		
		vos = new RuleQtaStatusVo[lst.size()];
		lst.toArray(vos);
		
		return vos;
	}
	
	public RuleQtaStatusVo getQuota(String sServerId, int iRuleId, String sProdId, double dNeedQuota, boolean bIsLockProd) {
		mLogger.debug("Server ask for Quota. ServerId:[" + sServerId + "], RuleId:[" + iRuleId + "], ProdId:[" + sProdId +"], NeedQuota:[" + dNeedQuota + "]");
		
		RuleQtaHandleCacheVo ruleQtaHandleCacheVo = null;
		RuleQtaStatusVo ruleQtaStatusCacheVo = new RuleQtaStatusVo();
		ruleQtaStatusCacheVo.setRuleId(iRuleId);
		ruleQtaStatusCacheVo.setProdId(sProdId);
		
		try {
			ruleQtaHandleCacheVo = getRuleQtaHandleCacheVoForUpdating(iRuleId, sProdId);
			
			if(ruleQtaHandleCacheVo != null) {
				// 已分配出去的Quota
				double dDispatchQuota = ruleQtaHandleCacheVo.getDispatchQuota();
				
				// 剩餘值
				double dRemainQuota = 0;
				if(ruleQtaHandleCacheVo.getWarnTriggerMk()) {
					dRemainQuota = ArithUtil.sub(ruleQtaHandleCacheVo.getWarnQuota(), dDispatchQuota);
				} else if(ruleQtaHandleCacheVo.getSubTriggerMk()) {
					dRemainQuota = ArithUtil.sub(ruleQtaHandleCacheVo.getSubQuota(), dDispatchQuota);
				} else if(ruleQtaHandleCacheVo.getRejTriggerMk()) {
					dRemainQuota = ArithUtil.sub(ruleQtaHandleCacheVo.getRejQuota(), dDispatchQuota);
				}
				
				// 需要的值小於剩於值
				if(dNeedQuota <= dRemainQuota) {
					double dThresholdQta = ruleQtaHandleCacheVo.getThresholdQta();
					// 剩餘的值 小於門檻
					if(dRemainQuota < dThresholdQta) {
						ruleQtaStatusCacheVo.setDispatchQta(dNeedQuota);
						dDispatchQuota = ArithUtil.add(dDispatchQuota, dNeedQuota);
						ruleQtaHandleCacheVo.setDispatchQuota(dDispatchQuota);
						
						dRemainQuota = ArithUtil.sub(dRemainQuota, dNeedQuota);
						mLogger.debug("QD剩餘值小於門檻， 只給需要的值:[" + dNeedQuota + "], RemainQuota:[" + dRemainQuota + "]");
					} else if(dNeedQuota > dThresholdQta) {
						ruleQtaStatusCacheVo.setDispatchQta(dNeedQuota);
						dDispatchQuota = ArithUtil.add(dDispatchQuota, dNeedQuota);
						ruleQtaHandleCacheVo.setDispatchQuota(dDispatchQuota);
						
						dRemainQuota = ArithUtil.sub(dRemainQuota, dNeedQuota);
						mLogger.debug("需要的值大於門檻， 給需要的值:[" + dNeedQuota + "], RemainQuota:[" + dRemainQuota + "]");
					} else {
						ruleQtaStatusCacheVo.setDispatchQta(dThresholdQta);
						dDispatchQuota = ArithUtil.add(dDispatchQuota, dThresholdQta);
						ruleQtaHandleCacheVo.setDispatchQuota(dDispatchQuota);
						
						dRemainQuota = ArithUtil.sub(dRemainQuota, dThresholdQta);
						mLogger.debug("QD剩餘值大於門檻， 給一門檻值:[" + dThresholdQta + "], RemainQuota:[" + dRemainQuota + "]");
					}
				} else {
					// 先Release 
					releaseRuleQtaHandleCacheVoFromUpdating(ruleQtaHandleCacheVo);
					ruleQtaHandleCacheVo = null;
					
					mLogger.debug("QD doesn't have enough Quota. Ask all Sar for DrawBack.");
					
					// 吐回
					notifyDrawBack(sServerId, iRuleId, sProdId);
					
					// Lock Thread
					mLogger.debug("Lock Thread. RuleId:[" + iRuleId + "], ProdId:[" + sProdId + "]");
					DrawBackThreadLockManager.getInstance().lockThread(sServerId, iRuleId, sProdId);
					
					ruleQtaStatusCacheVo = getQuotaAfterDrawBack(iRuleId, sProdId, dNeedQuota);
				}
			} else {
				// TODO 找不到VO 可能要丟Exception回SAR
				mLogger.error("Can't find Quota. RuleId:[" + iRuleId + "], ProdId:[" + sProdId + "]");
			}
		} finally {
			releaseRuleQtaHandleCacheVoFromUpdating(ruleQtaHandleCacheVo);
		}
		return ruleQtaStatusCacheVo;
	}
	
	private void notifyDrawBack(String sServerId, int iRuleId, String sProdId) {
		SacOutputBuffer sacOut = new SacOutputBuffer();
		try {
			sacOut.writeShortString(sServerId);
			sacOut.writeInt(iRuleId);
			sacOut.writeShortString(sProdId);
			
			HubNodeManager.getInstance().getSacLinkSevice().routePushMsg(QDCTopicConstant.NOTIFY_DRAWBACK_TOPIC, QDCTopicConstant.NOTIFY_ALL, sacOut.getByteArray(), SafeTcpLinkApi.PUSH_MSG_TYPE_GLOBAL);
		
		} catch (IOException ex) {
			ex.printStackTrace();
		} catch (SafeTcpLinkApiException ex) {
			ex.printStackTrace();
		}
	}
	
	public RuleQtaStatusVo getQuotaAfterDrawBack(int iRuleId, String sProdId, double dNeedQuota) {
		RuleQtaHandleCacheVo ruleQtaHandleCacheVo = null;
		RuleQtaStatusVo ruleQtaStatusVo = new RuleQtaStatusVo();
		ruleQtaStatusVo.setRuleId(iRuleId);
		ruleQtaStatusVo.setProdId(sProdId);
		
		try {
			ruleQtaHandleCacheVo = getRuleQtaHandleCacheVoForUpdating(iRuleId, sProdId);
			if(ruleQtaHandleCacheVo != null) {
				double dDispatchQuota = ruleQtaHandleCacheVo.getDispatchQuota();
				
				// 剩餘值
				double dRemainQuota = 0;
				if(ruleQtaHandleCacheVo.getWarnTriggerMk()) {
					dRemainQuota = ArithUtil.sub(ruleQtaHandleCacheVo.getWarnQuota(), dDispatchQuota);
				} else if(ruleQtaHandleCacheVo.getSubTriggerMk()) {
					dRemainQuota = ArithUtil.sub(ruleQtaHandleCacheVo.getSubQuota(), dDispatchQuota);
				} else if(ruleQtaHandleCacheVo.getRejTriggerMk()) {
					dRemainQuota = ArithUtil.sub(ruleQtaHandleCacheVo.getRejQuota(), dDispatchQuota);
				}
				
				// 需要的值小於剩於值
				if(dNeedQuota <= dRemainQuota) {
					dDispatchQuota = ArithUtil.add(dDispatchQuota, dNeedQuota);
					ruleQtaHandleCacheVo.setDispatchQuota(dDispatchQuota);
					
					dRemainQuota = ArithUtil.sub(dRemainQuota, dNeedQuota);
					
					mLogger.debug("吐回後Quota夠,給需要的. RuleId:[" + iRuleId + "], ProdId:[" + sProdId + "], RemainQuota:[" + dRemainQuota + "]");
				} else {
					// 額度還是要加
					dDispatchQuota = ArithUtil.add(dDispatchQuota, dNeedQuota);
					ruleQtaHandleCacheVo.setDispatchQuota(dDispatchQuota);
					
					int iTriggerType = 0;
					if(ruleQtaHandleCacheVo.getWarnTriggerMk()) {
						if(ruleQtaHandleCacheVo.getWarnQuota() < dDispatchQuota) {
							iTriggerType += CodeConstant.WARN_REACHED;
						}
					}
					if(ruleQtaHandleCacheVo.getSubTriggerMk()) {
						if(ruleQtaHandleCacheVo.getSubQuota() < dDispatchQuota) {
							iTriggerType += CodeConstant.SUBMIT_REACHED;
						}
					}
					if(ruleQtaHandleCacheVo.getRejTriggerMk()) {
						if(ruleQtaHandleCacheVo.getRejQuota() < dDispatchQuota) {
							iTriggerType += CodeConstant.REJECT_REACHED;
						}
					}
					
					ruleQtaStatusVo.setDispatchQta(dDispatchQuota);
					ruleQtaStatusVo.setTriggerType(iTriggerType);
					
					mLogger.debug("吐回後Quota不夠. RuleId:[" + iRuleId + "], ProdId:[" + sProdId + "], DispatchQuota:[" + dDispatchQuota + "], TriggerType:[" + iTriggerType + "]");
				}
			} else {
				mLogger.error("Can't find Quota. RuleId:[" + iRuleId + "], ProdId:[" + sProdId + "]");
			}
		} finally {
			releaseRuleQtaHandleCacheVoFromUpdating(ruleQtaHandleCacheVo);
		}
		return ruleQtaStatusVo;
	}

	public String[] getAllRuleQtaKeyString() {
		List<String> lst = new ArrayList<String>();
		List<Integer> lstRules = getAllRuleIds();
		
		for(Integer iRuleId : lstRules) {
			List<SafeCacheVo> lstVos = null;
			
			try {
				lstVos = getRuleQtaHandleCacheVosByRuleForReading(iRuleId);
				if(lstVos != null) {
					for(SafeCacheVo safeCacheVo : lstVos) {
						RuleQtaHandleCacheVo ruleQtaHandleCacheVo = (RuleQtaHandleCacheVo) safeCacheVo;
						
						String sKey = RuleQtaHandleCacheVo.getKeyString(ruleQtaHandleCacheVo.getRuleId(), ruleQtaHandleCacheVo.getProdId());
						lst.add(sKey);
					}
				}
			} finally {
				releaseRuleQtaHandleCacheVosFromReading(lstVos);
			}
		}
		
		String[] sKeys = new String[lst.size()];
		lst.toArray(sKeys);
		
		return sKeys;
	}
	
	public void drawBackToQD(String sServerId, int iRuleId, String sProdId, double dDrawBackValue) {
		RuleQtaHandleCacheVo ruleQtaHandleCacheVo = null;
		
		try {
			ruleQtaHandleCacheVo = getRuleQtaHandleCacheVoForUpdating(iRuleId, sProdId);
			
			if(ruleQtaHandleCacheVo != null) {
				// 已分配出去的Quota
				double dDispatchQuota = ruleQtaHandleCacheVo.getDispatchQuota();
				
				dDispatchQuota = ArithUtil.sub(dDispatchQuota, dDrawBackValue);
				
				ruleQtaHandleCacheVo.setDispatchQuota(dDispatchQuota);
				
				mLogger.debug("DrawBackValue add to Quota, RuleId:[" + iRuleId + "], ProdId:[" + sProdId + "], QD Used Quota:[" + dDispatchQuota + "]");
			}
		} finally {
			releaseRuleQtaHandleCacheVoFromUpdating(ruleQtaHandleCacheVo);
		}
		
		// Release Thread
		DrawBackThreadLockManager.getInstance().drawBackServerRecord(sServerId, iRuleId, sProdId);
	}
	
	public void rollBackToQD(int iRuleId, String sProdId, double dRollBackValue) {
		RuleQtaHandleCacheVo ruleQtaHandleCacheVo = null;
		
		try {
			ruleQtaHandleCacheVo = getRuleQtaHandleCacheVoForUpdating(iRuleId, sProdId);
			
			if(ruleQtaHandleCacheVo != null) {
				double dDispatchQuota = ruleQtaHandleCacheVo.getDispatchQuota();
				
				dDispatchQuota = ArithUtil.sub(dDispatchQuota, dRollBackValue);
				
				ruleQtaHandleCacheVo.setDispatchQuota(dDispatchQuota);
				
				mLogger.debug("Rollback value, RuleId:[" + iRuleId + "], ProdId:[" + sProdId + "], QD Used Quota:[" + dDispatchQuota + "]");
			}
		} finally {
			releaseRuleQtaHandleCacheVoFromUpdating(ruleQtaHandleCacheVo);
		}
	}
	
	public void updateZoomRuleQtaHandleCacheVoByNotify(RuleQtaHandleCacheVo ruleQtaHandleCacheVo) {
		int iRuleId = ruleQtaHandleCacheVo.getRuleId();
		String sProdId = ruleQtaHandleCacheVo.getProdId();
		
		RuleQtaHandleCacheVo vo = null;
		try {
			vo = getRuleQtaHandleCacheVoForUpdating(iRuleId, sProdId);
			if(vo != null) {
				if(!vo.getDrawBack()) {
					double dOldQuota = 0;
					double dNewQuota = 0;
					int iQuotaUnit = mapQuotaUnit.get(iRuleId);
					if(ruleQtaHandleCacheVo.getWarnQuota() != QDCodeConstant.QUOTA_ZOOM_NO_VALUE) {
						// 有放寬縮減值
						dOldQuota = vo.getWarnQuota();
						dNewQuota = ruleQtaHandleCacheVo.getWarnQuota();
						dNewQuota = ArithUtil.mul(dNewQuota, iQuotaUnit);
						if(dNewQuota > dOldQuota) {
							// 放寬
							vo.setWarnQuota(dNewQuota);
						} else if(dNewQuota == dOldQuota) {
							// 沒變動
						} else if(dNewQuota < dOldQuota) {
							// 縮減
							double dDispatchQuota = vo.getDispatchQuota();
							if(dNewQuota < dDispatchQuota) {
								mLogger.debug("Call DrawBack cause Zoom Warn Quota. RuleId:[" + iRuleId + "], ProdId:[" + sProdId + "], Quota:[" + dDispatchQuota + " -> " + dNewQuota + "]");
								
								releaseRuleQtaHandleCacheVoFromUpdating(vo);
								// 吐回
								DrawBackThreadLockManager.getInstance().lockThread(SacManager.getInstance().getSacId(), iRuleId, sProdId);
								
								vo = getRuleQtaHandleCacheVoForUpdating(iRuleId, sProdId);
								updateZoomRuleQtaHandleCacheVoByNotifyAfterDrawBack(ruleQtaHandleCacheVo, vo);
							} else {
								vo.setWarnQuota(dNewQuota);
							}
						}
					} 
					if(ruleQtaHandleCacheVo.getSubQuota() != QDCodeConstant.QUOTA_ZOOM_NO_VALUE) {
						dOldQuota = vo.getSubQuota();
						dNewQuota = ruleQtaHandleCacheVo.getSubQuota();
						dNewQuota = ArithUtil.mul(dNewQuota, iQuotaUnit);
						if(dNewQuota > dOldQuota) {
							// 放寬
							vo.setSubQuota(dNewQuota);
						} else if(dNewQuota == dOldQuota) {
							// 沒變動
						} else if(dNewQuota < dOldQuota) {
							// 縮減
							double dDispatchQuota = vo.getDispatchQuota();
							if(dNewQuota < dDispatchQuota) {
								mLogger.debug("Call DrawBack cause Zoom Sub Quota. RuleId:[" + iRuleId + "], ProdId:[" + sProdId + "], Quota:[" + dDispatchQuota + " -> " + dNewQuota + "]");
								
								releaseRuleQtaHandleCacheVoFromUpdating(vo);
								// 吐回
								DrawBackThreadLockManager.getInstance().lockThread(SacManager.getInstance().getSacId(), iRuleId, sProdId);
								
								vo = getRuleQtaHandleCacheVoForUpdating(iRuleId, sProdId);
								updateZoomRuleQtaHandleCacheVoByNotifyAfterDrawBack(ruleQtaHandleCacheVo, vo);
							} else {
								vo.setWarnQuota(dNewQuota);
							}
						}
					} 
					if(ruleQtaHandleCacheVo.getRejQuota() != QDCodeConstant.QUOTA_ZOOM_NO_VALUE) {
						dOldQuota = vo.getRejQuota();
						dNewQuota = ruleQtaHandleCacheVo.getRejQuota();
						dNewQuota = ArithUtil.mul(dNewQuota, iQuotaUnit);
						if(dNewQuota > dOldQuota) {
							// 放寬
							vo.setRejQuota(dNewQuota);
						} else if(dNewQuota == dOldQuota) {
							// 沒變動
						} else if(dNewQuota < dOldQuota) {
							// 縮減
							double dDispatchQuota = vo.getDispatchQuota();
							if(dNewQuota < dDispatchQuota) {
								mLogger.debug("Call DrawBack cause Zoom Rej Quota. RuleId:[" + iRuleId + "], ProdId:[" + sProdId + "], Quota:[" + dDispatchQuota + " -> " + dNewQuota + "]");
								
								releaseRuleQtaHandleCacheVoFromUpdating(vo);
								// 吐回
								DrawBackThreadLockManager.getInstance().lockThread(SacManager.getInstance().getSacId(), iRuleId, sProdId);
								
								vo = getRuleQtaHandleCacheVoForUpdating(iRuleId, sProdId);
								updateZoomRuleQtaHandleCacheVoByNotifyAfterDrawBack(ruleQtaHandleCacheVo, vo);
							} else {
								vo.setWarnQuota(dNewQuota);
							}
						}
					}
				} else {
					updateZoomRuleQtaHandleCacheVoByNotifyAfterDrawBack(ruleQtaHandleCacheVo, vo);
				}
			}
		} finally {
			releaseRuleQtaHandleCacheVoFromUpdating(vo);
		}
	}
	
	private void updateZoomRuleQtaHandleCacheVoByNotifyAfterDrawBack(RuleQtaHandleCacheVo ruleQtaHandleCacheVo, RuleQtaHandleCacheVo vo) {
		int iRuleId = ruleQtaHandleCacheVo.getRuleId();
		double dNewQuota = 0;
		int iQuotaUnit = mapQuotaUnit.get(iRuleId);
		if(ruleQtaHandleCacheVo.getWarnQuota() != QDCodeConstant.QUOTA_ZOOM_NO_VALUE) {
			dNewQuota = ruleQtaHandleCacheVo.getWarnQuota();
			dNewQuota = ArithUtil.mul(dNewQuota, iQuotaUnit);
			vo.setWarnQuota(dNewQuota);
		}
		if(ruleQtaHandleCacheVo.getSubQuota() != QDCodeConstant.QUOTA_ZOOM_NO_VALUE) {
			dNewQuota = ruleQtaHandleCacheVo.getSubQuota();
			dNewQuota = ArithUtil.mul(dNewQuota, iQuotaUnit);
			vo.setSubQuota(dNewQuota);
		}
		if(ruleQtaHandleCacheVo.getRejQuota() != QDCodeConstant.QUOTA_ZOOM_NO_VALUE) {
			dNewQuota = ruleQtaHandleCacheVo.getRejQuota();
			dNewQuota = ArithUtil.mul(dNewQuota, iQuotaUnit);
			vo.setRejQuota(dNewQuota);
		}
	}
	
	public List<Integer> getLockProdRuleIds() {
		return lstLockProdRuleIds;
	}
	
	public List<Integer> getNonLockProdRuldIds() {
		return lstNonLockProdRuleIds;
	}
	
	public List<Integer> getAllRuleIds() {
		List<Integer> lst = new ArrayList<Integer>();
		lst.addAll(lstLockProdRuleIds);
		lst.addAll(lstNonLockProdRuleIds);
		return lst;
	}
}
